package pingpongdojo.ingame;

import java.util.ArrayList;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.DrillDownAdapter;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.ViewPart;

import pingpongdojo.model.Game;
import pingpongdojo.model.GameManager;
import pingpongdojo.model.GameManager.GameListener;
import pingpongdojo.model.ModelFactory;

/**
 * This sample class demonstrates how to plug-in a new workbench view. The view
 * shows data obtained from the model. The sample creates a dummy model on the
 * fly, but a real implementation would connect to the model available either in
 * this or another plug-in (e.g. the workspace). The view is connected to the
 * model using a content provider.
 * <p>
 * The view uses a label provider to define how model objects should be
 * presented in the view. Each view can present the same model objects using
 * different labels and icons, if needed. Alternatively, a single label provider
 * can be shared between views in order to ensure that objects of the same type
 * are presented in the same way everywhere.
 * <p>
 */

public class InGameView extends ViewPart {

	/**
	 * The ID of the view as specified by the extension.
	 */
	public static final String ID = "pingpongdojo.ingame.InGameView";

	public static void init() {
		ModelFactory.getInstance().getGameManager()
				.addGameListener(new GameListener() {
					@Override
					public void onSetCurrentGame(Game game) {
						try {
							PlatformUI.getWorkbench()
									.getActiveWorkbenchWindow().getActivePage()
									.showView(ID);
						} catch (PartInitException e) {
							e.printStackTrace();
						}
					}

					@Override
					public void onGameUpdate(Game game) {
					}
				});

	}

	public static final String NO_GAME_IN_PROGRESS_MSG = "There is currently no game in progress.  Choose 'My Games' or 'New Game' from the PingPongDojo menu to start a game";

	private TreeViewer viewer;
	private DrillDownAdapter drillDownAdapter;

	private Action passToPartnerAction;
	private Action runTestsAction;
	private Action endGameAction;

	private Action doubleClickAction;

	/*
	 * The content provider class is responsible for providing objects to the
	 * view. It can wrap existing objects in adapters or simply return objects
	 * as-is. These objects may be sensitive to the current input of the view,
	 * or ignore it and always show the same content (like Task List, for
	 * example).
	 */

	class TreeObject implements IAdaptable {
		private String name;
		private TreeParent parent;
		private Image image;

		public TreeObject(String name, Image image) {
			this.name = name;
			this.image = image;
		}

		public String getName() {
			return name;
		}

		public void setParent(TreeParent parent) {
			this.parent = parent;
		}

		public TreeParent getParent() {
			return parent;
		}

		public String toString() {
			return name;
		}

		@SuppressWarnings("rawtypes")
		public Object getAdapter(Class key) {
			return null;
		}

		public Image getImage() {
			return image;
		}
	}

	class FileTreeObject extends TreeObject {
		private IFile file;

		public FileTreeObject(IFile file) {
			super(file.getName(), getImageForFile(file));
			this.file = file;
		}

		public IFile getFile() {
			return file;
		}

	}

	class TreeParent extends TreeObject {
		private ArrayList<TreeObject> children;

		public TreeParent(String name) {
			this(name, PlatformUI.getWorkbench().getSharedImages()
					.getImage(ISharedImages.IMG_OBJ_FOLDER));
		}

		public TreeParent(String name, Image image) {
			super(name, image);

			children = new ArrayList<TreeObject>();
		}

		public void addChild(TreeObject child) {
			children.add(child);
			child.setParent(this);
		}

		public void removeChild(TreeObject child) {
			children.remove(child);
			child.setParent(null);
		}

		public TreeObject[] getChildren() {
			return (TreeObject[]) children.toArray(new TreeObject[children
					.size()]);
		}

		public boolean hasChildren() {
			return children.size() > 0;
		}
	}

	class ViewContentProvider implements IStructuredContentProvider,
			ITreeContentProvider {
		private TreeParent invisibleRoot;
		private GameListener gameListener;

		public ViewContentProvider() {
			this.gameListener = new GameListener() {
				@Override
				public void onSetCurrentGame(Game game) {
					if (game != null) {
						viewer.setInput(game);
					} else {
						viewer.setInput(NO_GAME_IN_PROGRESS_MSG);
					}
					viewer.expandAll();
				}

				@Override
				public void onGameUpdate(Game game) {
					viewer.setInput(game);
					viewer.expandAll();
				}
			};

			ModelFactory.getInstance().getGameManager()
					.addGameListener(gameListener);
		}

		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
			if (newInput instanceof Game) {
				initialize((Game) newInput);
			} else if (newInput instanceof String) {
				initialize(null);
			}
		}

		@Override
		public void dispose() {
			ModelFactory.getInstance().getGameManager()
					.removeGameListener(gameListener);
		}

		public Object[] getElements(Object parent) {
			if (parent instanceof Game || parent instanceof String) {
				if (invisibleRoot == null)
					initialize(ModelFactory.getInstance().getGameManager()
							.getCurrentGame());
				return getChildren(invisibleRoot);
			}
			return getChildren(parent);
		}

		public Object getParent(Object child) {
			if (child instanceof TreeObject) {
				return ((TreeObject) child).getParent();
			}
			return null;
		}

		public Object[] getChildren(Object parent) {
			if (parent instanceof TreeParent) {
				return ((TreeParent) parent).getChildren();
			}
			return new Object[0];
		}

		public boolean hasChildren(Object parent) {
			if (parent instanceof TreeParent)
				return ((TreeParent) parent).hasChildren();
			return false;
		}

		private void initialize(Game game) {
			Image infoIcon = PlatformUI.getWorkbench().getSharedImages()
					.getImage(ISharedImages.IMG_OBJS_WARN_TSK);

			if (game == null) {
				TreeObject root = new TreeObject(NO_GAME_IN_PROGRESS_MSG,
						infoIcon);

				invisibleRoot = new TreeParent("");
				invisibleRoot.addChild(root);
				return;
			}

			String codePlayerName = game.getCodePlayer().getName();
			String testPlayerName = game.getTestPlayer().getName();

			TreeParent root = new TreeParent(game.getName(), PlatformUI
					.getWorkbench().getSharedImages()
					.getImage(ISharedImages.IMG_OBJ_ELEMENT));

			TreeParent codeParent = new TreeParent("Code (" + codePlayerName
					+ ")");

			if (game.getCodeFiles().size() == 0) {
				TreeObject child = new TreeObject(
						"No code files in game.  To add a file right click on a code file from your project and choose 'Add to current game' under PingPongDojo.",
						infoIcon);
				codeParent.addChild(child);
			}
			for (IFile file : game.getCodeFiles()) {
				TreeObject child = new FileTreeObject(file);
				codeParent.addChild(child);
			}

			TreeParent testParent = new TreeParent("Test (" + testPlayerName
					+ ")");

			if (game.getTestFiles().size() == 0) {
				TreeObject child = new TreeObject(
						"No test files in game.  To add a file right click on a test file from your project and choose 'Add to current game' under PingPongDojo.",
						infoIcon);
				testParent.addChild(child);
			}
			for (IFile file : game.getTestFiles()) {
				TreeObject to1 = new FileTreeObject(file);
				testParent.addChild(to1);
			}

			switch (game.getCurrentObjective()) {
			case CODE:
				root.addChild(codeParent);
				root.addChild(testParent);
				break;
			case TEST:
				root.addChild(testParent);
				root.addChild(codeParent);
				break;
			}

			invisibleRoot = new TreeParent("");
			invisibleRoot.addChild(root);
		}
	}

	class ViewLabelProvider extends LabelProvider {

		public String getText(Object obj) {
			return obj.toString();
		}

		public Image getImage(Object obj) {
			if (obj instanceof TreeObject) {
				TreeObject treeObject = (TreeObject) obj;
				return treeObject.getImage();
			}
			String imageKey = ISharedImages.IMG_OBJ_ELEMENT;
			return PlatformUI.getWorkbench().getSharedImages()
					.getImage(imageKey);
		}
	}

	// class NameSorter extends ViewerSorter {
	// }

	/**
	 * The constructor.
	 */
	public InGameView() {
	}

	/**
	 * This is a callback that will allow us to create the viewer and initialize
	 * it.
	 */
	public void createPartControl(Composite parent) {
		if (viewer != null) {
			return;
		}
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		drillDownAdapter = new DrillDownAdapter(viewer);

		viewer.setContentProvider(new ViewContentProvider());
		viewer.setLabelProvider(new ViewLabelProvider());
		// viewer.setSorter(new NameSorter());

		Game currentGame = ModelFactory.getInstance().getGameManager()
				.getCurrentGame();
		if (currentGame != null) {
			viewer.setInput(currentGame);
		} else {
			viewer.setInput(NO_GAME_IN_PROGRESS_MSG);
		}

		makeActions();
		hookContextMenu();
		hookDoubleClickAction();
		contributeToActionBars();

		ModelFactory.getInstance().getGameManager()
				.addGameListener(new GameListener() {
					@Override
					public void onSetCurrentGame(Game game) {
						boolean enabled = (game != null);
						passToPartnerAction.setEnabled(enabled);
						runTestsAction.setEnabled(enabled);
						endGameAction.setEnabled(enabled);
					}

					@Override
					public void onGameUpdate(Game game) {
					}
				});
		
		viewer.expandAll();
	}

	private void hookContextMenu() {
		MenuManager menuMgr = new MenuManager("#PopupMenu");
		menuMgr.setRemoveAllWhenShown(true);
		menuMgr.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				InGameView.this.fillContextMenu(manager);
			}
		});
		Menu menu = menuMgr.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, viewer);
	}

	private void contributeToActionBars() {
		IActionBars bars = getViewSite().getActionBars();
		fillLocalPullDown(bars.getMenuManager());
		fillLocalToolBar(bars.getToolBarManager());
	}

	private void fillLocalPullDown(IMenuManager manager) {
		manager.removeAll();
		manager.add(passToPartnerAction);
		manager.add(runTestsAction);
		manager.add(new Separator());
		manager.add(endGameAction);
	}

	private void fillContextMenu(IMenuManager manager) {
		manager.removeAll();
		manager.add(passToPartnerAction);
		manager.add(runTestsAction);
		manager.add(endGameAction);
		manager.add(new Separator());
		drillDownAdapter.addNavigationActions(manager);
		// Other plug-ins can contribute there actions here
		manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

	private void fillLocalToolBar(IToolBarManager manager) {
		manager.removeAll();
		manager.add(passToPartnerAction);
		manager.add(runTestsAction);
		manager.add(endGameAction);
		manager.add(new Separator());
		drillDownAdapter.addNavigationActions(manager);
	}

	private void makeActions() {
		boolean enabled = false;
		if (ModelFactory.getInstance().getGameManager().getCurrentGame() != null) {
			enabled = true;
		}

		passToPartnerAction = new Action() {
			public void run() {
				GameManager gameManager = ModelFactory.getInstance()
						.getGameManager();

				gameManager.getCurrentGame().passTurn();
				gameManager.setCurrentGame(null);
			}
		};

		passToPartnerAction.setEnabled(enabled);
		passToPartnerAction.setText("Pass to partner");
		passToPartnerAction.setToolTipText("Pass to partner");
		passToPartnerAction.setImageDescriptor(PlatformUI.getWorkbench()
				.getSharedImages()
				.getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD));

		// //////////////////////////////////////////////////////////////////////

		Image image = new Image(Display.getCurrent(), this.getClass()
				.getResourceAsStream("junit.png"));
		runTestsAction = new Action() {
			public void run() {
				showMessage("Feature not implemented. JUnit tests would run now.");
			}
		};

		runTestsAction.setEnabled(enabled);
		runTestsAction.setText("Run tests");
		runTestsAction.setToolTipText("Run tests");
		runTestsAction.setImageDescriptor(ImageDescriptor
				.createFromImage(image));

		// //////////////////////////////////////////////////////////////////////

		endGameAction = new Action() {
			public void run() {
				GameManager gameManager = ModelFactory.getInstance()
						.getGameManager();

				String gameName = gameManager.getCurrentGame().getName();
				String message = "Are you certain you would like to end the "
						+ gameName + " game?";

				if (MessageDialog.openConfirm(viewer.getControl().getShell(),
						"PingPongDojo: End Game", message)) {
					gameManager.endGame(gameManager.getCurrentGame());
				}
			}
		};

		endGameAction.setEnabled(enabled);
		endGameAction.setText("End game");
		endGameAction.setToolTipText("End the game");
		endGameAction.setImageDescriptor(PlatformUI.getWorkbench()
				.getSharedImages()
				.getImageDescriptor(ISharedImages.IMG_OBJS_TASK_TSK));

		// //////////////////////////////////////////////////////////////////////

		doubleClickAction = new Action() {
			public void run() {
				ISelection selection = viewer.getSelection();
				Object obj = ((IStructuredSelection) selection)
						.getFirstElement();
				if (obj instanceof FileTreeObject) {
					FileTreeObject fileTreeObject = (FileTreeObject) obj;
					IWorkbenchPage page = PlatformUI.getWorkbench()
							.getActiveWorkbenchWindow().getActivePage();
					IFile file = fileTreeObject.getFile();
					IEditorDescriptor desc = PlatformUI.getWorkbench()
							.getEditorRegistry()
							.getDefaultEditor(file.getName());
					try {
						page.openEditor(new FileEditorInput(file), desc.getId());
					} catch (PartInitException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}

			}
		};
	}

	private void hookDoubleClickAction() {
		viewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				doubleClickAction.run();
			}
		});
	}

	private void showMessage(String message) {
		MessageDialog.openInformation(viewer.getControl().getShell(),
				"PingPongDojo", message);
	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		viewer.getControl().setFocus();
	}

	public static Image getImageForFile(IFile file) {
		IContentType contentType = IDE.getContentType(file);
		ImageDescriptor imageDescriptor = PlatformUI.getWorkbench()
				.getEditorRegistry()
				.getImageDescriptor(file.getName(), contentType);
		return imageDescriptor.createImage();
	}

}